using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.UI;

static class UIReuseItemGenerator
{
    const string EXCLUDE = "~";
    const string INCLUDE = "#";

    public static void Generate(GameObject target, string ctrlPath, string variablePath)
    {
        if (!target) return;

        var reuseItemInfo = GetUIReuseItemInfo(target);

        GenerateCtrl(reuseItemInfo, ctrlPath);
        GenerateVariable(reuseItemInfo, variablePath);
        SerializableVariables(target, reuseItemInfo);
    }

    static void SerializableVariables(GameObject prefab, UIReuseItemInfo reuseItemInfo)
    {
        if (prefab)
        {
            if (prefab.TryGetComponent<UIReuseItem>(out var component))
            {
                // 使用 UIVariableHelper 来存储变量
                var variableHelper = prefab.GetOrAddComponent<UIVariableHelper>();
                var variables = new List<GameObject>();
                foreach (var variable in reuseItemInfo.Variables)
                    foreach (var gameObjectInfo in variable.GameObjectInfos)
                        variables.Add(gameObjectInfo.GameObject);
                variableHelper.Variables = variables;
            }
        }
    }

    static UIReuseItemInfo GetUIReuseItemInfo(GameObject target)
    {
        var result = new UIReuseItemInfo()
        {
            Name = target.name,
            Variables = new List<VariableInfo>()
        };

        TraverseTransform(target.transform, result.Variables);
        foreach (var variable in result.Variables)
            variable.GameObjectInfos.Sort((a, b) => a.FieldName.CompareTo(b.FieldName));
        result.Variables.Sort((a, b) => a.FieldName.CompareTo(b.FieldName));

        return result;
    }

    static void TraverseTransform(Transform transform, List<VariableInfo> variables)
    {
        for (int i = 0; i < transform.childCount; i++)
        {
            var child = transform.GetChild(i);
            if (!child) continue;
            RecursionTransform(transform.GetChild(i), variables);
        }
    }


    static void RecursionTransform(Transform transform, List<VariableInfo> variables)
    {
        if (string.IsNullOrEmpty(transform.name)) return;

        var excludeChild = transform.name.StartsWith(EXCLUDE);

        AddVariable(transform, variables, excludeChild);

        if (excludeChild) return;

        for (int i = 0; i < transform.childCount; i++)
        {
            var child = transform.GetChild(i);
            if (!child) continue;

            RecursionTransform(transform.GetChild(i), variables);
        }
    }

    static void AddVariable(Transform transform, List<VariableInfo> variables, bool excludeChild)
    {
        var name = excludeChild ? transform.name[1..] : transform.name;
        if (!name.StartsWith(INCLUDE)) return;

        const string m_VariableNameRegex = @"^~?#(?<variableType>[A-Za-z0-9]+)_(?<variableName>[A-Za-z0-9]+)(|_(?<variableIndex>\d+))$";
        Match nameMatch = new Regex(m_VariableNameRegex).Match(transform.name);
        string variableType = nameMatch.Groups["variableType"].Value;
        string variableName = nameMatch.Groups["variableName"].Value;
        string variableIndex = nameMatch.Groups["variableIndex"].Value;

        if (string.IsNullOrEmpty(variableType))
        {
            Debug.LogWarning($"Ԥ���Ͽؼ�({transform.name})���ֲ����ϴ������ɹ���: ǰ׺����Ϊ��");
            return;
        }
        if (string.IsNullOrEmpty(variableName))
        {
            Debug.LogWarning($"Ԥ���Ͽؼ�({transform.name})���ֲ����ϴ������ɹ���: ������Ϊ��");
            return;
        }

        var isInterface = false;
        var dic = UIVariableGenerator.TypeMapping;
        if (!dic.TryGetValue(variableType, out var componentType))
        {
            if (variableType == "RL")
            {
                componentType = "UIReuseList";
                isInterface = true;
            }
            else if (variableType == "SP")
            {
                if (transform.TryGetComponent<ScriptHelper>(out var component))
                {
                    componentType = component.ScriptName;
                }
                else if (transform.TryGetComponent<GameMonoBehaviour>(out var mono))
                {
                    componentType = mono.GameMonoBehaviourName;
                }
                else
                {
                    componentType = "GameObject";
                }
            }
            else
            {
                componentType = "GameObject";
            }
        }

        var isArray = !string.IsNullOrEmpty(variableIndex);

        if (isArray)
        {
            var fieldName = $"{variableType}{variableName}s";
            var fieldType = componentType;

            var gameObjectInfo = new GameObjectInfo()
            {
                FieldName = fieldName,
                GameObject = transform.gameObject,
            };
            var index = variables.FindIndex(v => v.FieldName == fieldName);
            if (index >= 0)
            {
                var variable = variables[index];
                variable.GameObjectInfos.Add(gameObjectInfo);
            }
            else
            {
                variables.Add(new VariableInfo()
                {
                    FieldName = fieldName,
                    FieldType = fieldType,
                    IsArray = isArray,
                    IsInterface = isInterface,
                    GameObjectInfos = new() { gameObjectInfo }
                });
            }
        }
        else
        {
            var fieldName = $"{variableType}{variableName}";
            var fieldType = componentType;

            var gameObjectInfo = new GameObjectInfo()
            {
                FieldName = fieldName,
                GameObject = transform.gameObject,
            };

            var index = variables.FindIndex(v => v.FieldName == fieldName);
            if (index >= 0)
            {
                var variable = variables[index];
                var already = variable.GameObjectInfos.Last().GameObject;
                var current = transform.gameObject;
                throw new ReuseItemGenerateException($"cs��������ʧ�ܣ� �ؼ�cs�ֶ����ظ�������,�ؼ�����: {componentType}, ��������: {variableType}, ������: {variableName}", already, current);
            }
            else
            {
                variables.Add(new VariableInfo()
                {
                    FieldName = fieldName,
                    FieldType = fieldType,
                    IsArray = isArray,
                    IsInterface = isInterface,
                    GameObjectInfos = new() { gameObjectInfo }
                });
            }
        }
    }

    static void GenerateCtrl(UIReuseItemInfo reuseItemInfo, string file)
    {
        if (File.Exists(file)) return;

        using var writer = new CodeWriter(file);
        // class
        writer.Bracket($"public partial class {reuseItemInfo.Name}Ctrl : UIReuseItemCtrl");
        {
            var btns = reuseItemInfo.Variables.Where(v => v.FieldType == typeof(Button).Name);
            // OnEnable
            writer.Bracket("protected override void OnEnable()");
            {
                writer.WriteLine();
                foreach (var btn in btns)
                    writer.WriteLine($"{btn.FieldName}.onClick.AddListener(On{btn.FieldName}Click);");
                writer.EndBracket();
            }

            // OnDisable
            writer.Bracket("protected override void OnDisable()");
            {
                writer.WriteLine();
                foreach (var btn in btns)
                    writer.WriteLine($"{btn.FieldName}.onClick.RemoveListener(On{btn.FieldName}Click);");
                writer.EndBracket();
            }

            // OnRefresh
            writer.Bracket("protected override void OnRefresh()");
            {
                writer.WriteLine();
                writer.EndBracket();
            }

            // Btn Methods
            foreach (var btn in btns)
            {
                writer.Bracket($"void On{btn.FieldName}Click()");
                {
                    writer.WriteLine();
                    writer.EndBracket();
                }
            }

            writer.EndBracket();
        }
    }

    static void GenerateVariable(UIReuseItemInfo reuseItemInfo, string file)
    {
        using var writer = new CodeWriter(file);
        writer.WriteLine("// Do not Edit it!!!");
        writer.WriteLine();
        // namespace
        writer.WriteLine("using System.Collections.Generic;");
        writer.WriteLine("using UnityEngine;");
        writer.WriteLine("using UnityEngine.UI;");
        writer.WriteLine("using UnityEngine.EventSystems;");
        writer.WriteLine("using Spine.Unity;");
        writer.WriteLine();

        // UIReuseItemKeys
        writer.Bracket("public static partial class UIReuseItemKeys");
        {
            writer.WriteLine($"public static readonly UIReuseItemKey {reuseItemInfo.Name} = new(() => new {reuseItemInfo.Name}Ctrl());");
            writer.EndBracket();
        }
        writer.WriteLine();

        // class
        writer.Bracket($"public partial class {reuseItemInfo.Name}Ctrl : IUIReuseItemBinder");
        {
            var variables = reuseItemInfo.Variables;
            // fields
            for (int i = 0, length = variables.Count; i < length; i++)
            {
                var variable = variables[i];
                var fieldType = variable.IsInterface ? $"I{variable.FieldType}" : variable.FieldType;
                if (variable.IsArray)
                    writer.WriteLine($"List<{fieldType}> {variable.FieldName} = new({variable.GameObjectInfos.Count});");
                else
                    writer.WriteLine($"{fieldType} {variable.FieldName};");
            }
            writer.WriteLine();

            // bindings
            writer.Bracket("void IUIReuseItemBinder.Binding(IUIGetter getter)");
            {
                var gameObjectIndex = 0;
                for (int i = 0, length = variables.Count; i < length; i++)
                {
                    var variable = variables[i];
                    var arrayCount = variable.GameObjectInfos.Count;
                    if (variable.IsArray)
                    {
                        writer.WriteLine($"{variable.FieldName}.Clear();");
                        writer.WriteLine($"for (int i = 0; i < {arrayCount}; i++)");
                        {
                            writer.Indent();
                            if (variable.FieldType == typeof(GameObject).Name)
                                writer.WriteLine($"{variable.FieldName}.Add(getter.GetGameObject({gameObjectIndex} + i));");
                            else
                                writer.WriteLine($"{variable.FieldName}.Add(getter.GetComponent<{variable.FieldType}>({gameObjectIndex} + i));");
                            writer.Dedent();
                        }
                    }
                    else
                    {
                        if (variable.FieldType == typeof(GameObject).Name)
                            writer.WriteLine($"{variable.FieldName} = getter.GetGameObject({gameObjectIndex});");
                        else
                            writer.WriteLine($"{variable.FieldName} = getter.GetComponent<{variable.FieldType}>({gameObjectIndex});");
                    }
                    gameObjectIndex += arrayCount;
                }
                writer.EndBracket();
            }
            writer.EndBracket();
        }
    }

    struct UIReuseItemInfo
    {
        public string Name;
        public List<VariableInfo> Variables;
        public UIReuseItemInfo(string name, List<VariableInfo> variables)
        {
            Name = name;
            Variables = variables;
        }
    }

    struct GameObjectInfo
    {
        public string FieldName;
        public GameObject GameObject;
    }

    struct VariableInfo
    {
        public string FieldName;
        public string FieldType;
        public List<GameObjectInfo> GameObjectInfos;
        public bool IsArray;
        public bool IsInterface;
    }

}

class ReuseItemGenerateException : System.Exception
{
    public GameObject already { get; private set; }
    public GameObject current { get; private set; }

    public ReuseItemGenerateException(string message, GameObject already, GameObject current) : base(message)
    {
        this.already = already;
        this.current = current;
    }
}